// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/web_contents/aura/overscroll_window_animation.h"

#include <algorithm>
#include <utility>

#include "base/i18n/rtl.h"
#include "content/browser/web_contents/aura/shadow_layer_delegate.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"

namespace content {

namespace {

    OverscrollWindowAnimation::Direction GetDirectionForMode(OverscrollMode mode)
    {
        if (mode == (base::i18n::IsRTL() ? OVERSCROLL_EAST : OVERSCROLL_WEST))
            return OverscrollWindowAnimation::SLIDE_FRONT;
        if (mode == (base::i18n::IsRTL() ? OVERSCROLL_WEST : OVERSCROLL_EAST))
            return OverscrollWindowAnimation::SLIDE_BACK;
        return OverscrollWindowAnimation::SLIDE_NONE;
    }

} // namespace

OverscrollWindowAnimation::OverscrollWindowAnimation(Delegate* delegate)
    : delegate_(delegate)
    , direction_(SLIDE_NONE)
    , overscroll_cancelled_(false)
{
    DCHECK(delegate_);
}

OverscrollWindowAnimation::~OverscrollWindowAnimation()
{
}

void OverscrollWindowAnimation::CancelSlide()
{
    overscroll_cancelled_ = true;
    // Listen to the animation of the main window.
    bool main_window_is_front = direction_ == SLIDE_BACK;
    AnimateTranslation(GetBackLayer(), 0, !main_window_is_front);
    AnimateTranslation(GetFrontLayer(), 0, main_window_is_front);
}

float OverscrollWindowAnimation::GetTranslationForOverscroll(float delta_x)
{
    DCHECK(direction_ != SLIDE_NONE);
    const float bounds_width = GetVisibleBounds().width();
    if (direction_ == SLIDE_FRONT)
        return std::max(-bounds_width, delta_x);
    else
        return std::min(bounds_width, delta_x);
}

gfx::Rect OverscrollWindowAnimation::GetVisibleBounds() const
{
    return delegate_->GetMainWindow()->bounds();
}

bool OverscrollWindowAnimation::OnOverscrollUpdate(float delta_x,
    float delta_y)
{
    if (direction_ == SLIDE_NONE)
        return false;
    gfx::Transform front_transform;
    gfx::Transform back_transform;
    float translate_x = GetTranslationForOverscroll(delta_x);
    front_transform.Translate(translate_x, 0);
    back_transform.Translate(translate_x / 2, 0);
    GetFrontLayer()->SetTransform(front_transform);
    GetBackLayer()->SetTransform(back_transform);
    return true;
}

void OverscrollWindowAnimation::OnImplicitAnimationsCompleted()
{
    if (overscroll_cancelled_) {
        slide_window_.reset();
        delegate_->OnOverscrollCancelled();
        overscroll_cancelled_ = false;
    } else {
        delegate_->OnOverscrollCompleted(std::move(slide_window_));
    }
    direction_ = SLIDE_NONE;
}

void OverscrollWindowAnimation::OnOverscrollModeChange(
    OverscrollMode old_mode,
    OverscrollMode new_mode)
{
    DCHECK_NE(old_mode, new_mode);
    Direction new_direction = GetDirectionForMode(new_mode);
    if (new_direction == SLIDE_NONE) {
        // The user cancelled the in progress animation.
        if (is_active())
            CancelSlide();
        return;
    }
    if (is_active()) {
        slide_window_->layer()->GetAnimator()->StopAnimating();
        delegate_->GetMainWindow()->layer()->GetAnimator()->StopAnimating();
    }
    gfx::Rect slide_window_bounds = gfx::Rect(GetVisibleBounds().size());
    if (new_direction == SLIDE_FRONT) {
        slide_window_bounds.Offset(base::i18n::IsRTL()
                ? -slide_window_bounds.width()
                : slide_window_bounds.width(),
            0);
    } else {
        slide_window_bounds.Offset(base::i18n::IsRTL()
                ? slide_window_bounds.width() / 2
                : -slide_window_bounds.width() / 2,
            0);
    }
    slide_window_ = new_direction == SLIDE_FRONT
        ? delegate_->CreateFrontWindow(slide_window_bounds)
        : delegate_->CreateBackWindow(slide_window_bounds);
    if (!slide_window_) {
        // Cannot navigate, do not start an overscroll gesture.
        direction_ = SLIDE_NONE;
        return;
    }
    overscroll_cancelled_ = false;
    direction_ = new_direction;
    shadow_.reset(new ShadowLayerDelegate(GetFrontLayer()));
}

void OverscrollWindowAnimation::OnOverscrollComplete(
    OverscrollMode overscroll_mode)
{
    if (!is_active())
        return;
    delegate_->OnOverscrollCompleting();
    int content_width = GetVisibleBounds().width();
    float translate_x;
    if ((base::i18n::IsRTL() && direction_ == SLIDE_FRONT) || (!base::i18n::IsRTL() && direction_ == SLIDE_BACK)) {
        translate_x = content_width;
    } else {
        translate_x = -content_width;
    }
    // Listen to the animation of the main window.
    bool main_window_is_front = direction_ == SLIDE_BACK;
    AnimateTranslation(GetBackLayer(), translate_x / 2, !main_window_is_front);
    AnimateTranslation(GetFrontLayer(), translate_x, main_window_is_front);
}

void OverscrollWindowAnimation::AnimateTranslation(ui::Layer* layer,
    float translate_x,
    bool listen_for_completion)
{
    gfx::Transform transform;
    transform.Translate(translate_x, 0);
    ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
    settings.SetPreemptionStrategy(
        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    settings.SetTweenType(gfx::Tween::EASE_OUT);
    if (listen_for_completion)
        settings.AddObserver(this);
    layer->SetTransform(transform);
}

ui::Layer* OverscrollWindowAnimation::GetFrontLayer() const
{
    DCHECK(direction_ != SLIDE_NONE);
    if (direction_ == SLIDE_FRONT) {
        DCHECK(slide_window_);
        return slide_window_->layer();
    }
    return delegate_->GetMainWindow()->layer();
}

ui::Layer* OverscrollWindowAnimation::GetBackLayer() const
{
    DCHECK(direction_ != SLIDE_NONE);
    if (direction_ == SLIDE_BACK) {
        DCHECK(slide_window_);
        return slide_window_->layer();
    }
    return delegate_->GetMainWindow()->layer();
}

} // namespace content
